// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <memory>

#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_sample_types.h"
#include "media/base/channel_layout.h"
#include "media/base/fake_audio_render_callback.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

static const int kChannels = 6;
static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_5_1;
// Use a buffer size which is intentionally not a multiple of kChannelAlignment.
static const int kFrameCount = media::AudioBus::kChannelAlignment * 32 - 1;
static const int kSampleRate = 48000;

class AudioBusTest : public testing::Test {
public:
    AudioBusTest() { }
    ~AudioBusTest() override
    {
        for (size_t i = 0; i < data_.size(); ++i)
            base::AlignedFree(data_[i]);
    }

    void VerifyChannelAndFrameCount(AudioBus* bus)
    {
        EXPECT_EQ(kChannels, bus->channels());
        EXPECT_EQ(kFrameCount, bus->frames());
    }

    void VerifyArrayIsFilledWithValue(const float data[], int size, float value)
    {
        for (int i = 0; i < size; ++i)
            ASSERT_FLOAT_EQ(value, data[i]) << "i=" << i;
    }

    // Verify values for each channel in |result| are within |epsilon| of
    // |expected|.  If |epsilon| exactly equals 0, uses FLOAT_EQ macro.
    void VerifyAreEqualWithEpsilon(const AudioBus* result,
        const AudioBus* expected,
        float epsilon)
    {
        ASSERT_EQ(expected->channels(), result->channels());
        ASSERT_EQ(expected->frames(), result->frames());
        for (int ch = 0; ch < result->channels(); ++ch) {
            for (int i = 0; i < result->frames(); ++i) {
                SCOPED_TRACE(base::StringPrintf("ch=%d, i=%d", ch, i));

                if (epsilon == 0) {
                    ASSERT_FLOAT_EQ(expected->channel(ch)[i], result->channel(ch)[i]);
                } else {
                    ASSERT_NEAR(expected->channel(ch)[i], result->channel(ch)[i],
                        epsilon);
                }
            }
        }
    }

    // Verify values for each channel in |result| against |expected|.
    void VerifyAreEqual(const AudioBus* result, const AudioBus* expected)
    {
        VerifyAreEqualWithEpsilon(result, expected, 0);
    }

    // Read and write to the full extent of the allocated channel data.  Also test
    // the Zero() method and verify it does as advertised.  Also test data if data
    // is 16-byte aligned as advertised (see kChannelAlignment in audio_bus.h).
    void VerifyReadWriteAndAlignment(AudioBus* bus)
    {
        for (int i = 0; i < bus->channels(); ++i) {
            // Verify that the address returned by channel(i) is a multiple of
            // AudioBus::kChannelAlignment.
            ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(bus->channel(i)) & (AudioBus::kChannelAlignment - 1));

            // Write into the channel buffer.
            std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i);
        }

        for (int i = 0; i < bus->channels(); ++i)
            VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), i);

        bus->Zero();
        for (int i = 0; i < bus->channels(); ++i)
            VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), 0);
    }

    // Verify copying to and from |bus1| and |bus2|.
    void CopyTest(AudioBus* bus1, AudioBus* bus2)
    {
        // Fill |bus1| with dummy data.
        for (int i = 0; i < bus1->channels(); ++i)
            std::fill(bus1->channel(i), bus1->channel(i) + bus1->frames(), i);

        // Verify copy from |bus1| to |bus2|.
        bus2->Zero();
        bus1->CopyTo(bus2);
        VerifyAreEqual(bus1, bus2);

        // Verify copy from |bus2| to |bus1|.
        bus1->Zero();
        bus2->CopyTo(bus1);
        VerifyAreEqual(bus2, bus1);
    }

protected:
    std::vector<float*> data_;

    DISALLOW_COPY_AND_ASSIGN(AudioBusTest);
};

// Verify basic Create(...) method works as advertised.
TEST_F(AudioBusTest, Create)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);
    VerifyChannelAndFrameCount(bus.get());
    VerifyReadWriteAndAlignment(bus.get());
}

// Verify Create(...) using AudioParameters works as advertised.
TEST_F(AudioBusTest, CreateUsingAudioParameters)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(
        AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout,
            kSampleRate, 32, kFrameCount));
    VerifyChannelAndFrameCount(bus.get());
    VerifyReadWriteAndAlignment(bus.get());
}

// Verify an AudioBus created via wrapping a vector works as advertised.
TEST_F(AudioBusTest, WrapVector)
{
    data_.reserve(kChannels);
    for (int i = 0; i < kChannels; ++i) {
        data_.push_back(static_cast<float*>(base::AlignedAlloc(
            sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment)));
    }

    std::unique_ptr<AudioBus> bus = AudioBus::WrapVector(kFrameCount, data_);
    VerifyChannelAndFrameCount(bus.get());
    VerifyReadWriteAndAlignment(bus.get());
}

// Verify an AudioBus created via wrapping a memory block works as advertised.
TEST_F(AudioBusTest, WrapMemory)
{
    AudioParameters params(
        AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32,
        kFrameCount);
    int data_size = AudioBus::CalculateMemorySize(params);
    std::unique_ptr<float, base::AlignedFreeDeleter> data(static_cast<float*>(
        base::AlignedAlloc(data_size, AudioBus::kChannelAlignment)));

    // Fill the memory with a test value we can check for after wrapping.
    static const float kTestValue = 3;
    std::fill(
        data.get(), data.get() + data_size / sizeof(*data.get()), kTestValue);

    std::unique_ptr<AudioBus> bus = AudioBus::WrapMemory(params, data.get());
    // Verify the test value we filled prior to wrapping.
    for (int i = 0; i < bus->channels(); ++i)
        VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), kTestValue);
    VerifyChannelAndFrameCount(bus.get());
    VerifyReadWriteAndAlignment(bus.get());

    // Verify the channel vectors lie within the provided memory block.
    EXPECT_GE(bus->channel(0), data.get());
    EXPECT_LT(bus->channel(bus->channels() - 1) + bus->frames(),
        data.get() + data_size / sizeof(*data.get()));
}

// Simulate a shared memory transfer and verify results.
TEST_F(AudioBusTest, CopyTo)
{
    // Create one bus with AudioParameters and the other through direct values to
    // test for parity between the Create() functions.
    AudioParameters params(
        AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 32,
        kFrameCount);
    std::unique_ptr<AudioBus> bus1 = AudioBus::Create(kChannels, kFrameCount);
    std::unique_ptr<AudioBus> bus2 = AudioBus::Create(params);

    {
        SCOPED_TRACE("Created");
        CopyTest(bus1.get(), bus2.get());
    }
    {
        SCOPED_TRACE("Wrapped Vector");
        // Try a copy to an AudioBus wrapping a vector.
        data_.reserve(kChannels);
        for (int i = 0; i < kChannels; ++i) {
            data_.push_back(static_cast<float*>(base::AlignedAlloc(
                sizeof(*data_[i]) * kFrameCount, AudioBus::kChannelAlignment)));
        }

        bus2 = AudioBus::WrapVector(kFrameCount, data_);
        CopyTest(bus1.get(), bus2.get());
    }
    {
        SCOPED_TRACE("Wrapped Memory");
        // Try a copy to an AudioBus wrapping a memory block.
        std::unique_ptr<float, base::AlignedFreeDeleter> data(static_cast<float*>(
            base::AlignedAlloc(AudioBus::CalculateMemorySize(params),
                AudioBus::kChannelAlignment)));

        bus2 = AudioBus::WrapMemory(params, data.get());
        CopyTest(bus1.get(), bus2.get());
    }
}

// Verify Zero() and ZeroFrames(...) utility methods work as advertised.
TEST_F(AudioBusTest, Zero)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);

    // Fill the bus with dummy data.
    for (int i = 0; i < bus->channels(); ++i)
        std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);
    EXPECT_FALSE(bus->AreFramesZero());

    // Zero first half the frames of each channel.
    bus->ZeroFrames(kFrameCount / 2);
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("First Half Zero");
        VerifyArrayIsFilledWithValue(bus->channel(i), kFrameCount / 2, 0);
        VerifyArrayIsFilledWithValue(bus->channel(i) + kFrameCount / 2,
            kFrameCount - kFrameCount / 2, i + 1);
    }
    EXPECT_FALSE(bus->AreFramesZero());

    // Fill the bus with dummy data.
    for (int i = 0; i < bus->channels(); ++i)
        std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);

    // Zero the last half of the frames.
    bus->ZeroFramesPartial(kFrameCount / 2, kFrameCount - kFrameCount / 2);
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("Last Half Zero");
        VerifyArrayIsFilledWithValue(bus->channel(i) + kFrameCount / 2,
            kFrameCount - kFrameCount / 2, 0);
        VerifyArrayIsFilledWithValue(bus->channel(i), kFrameCount / 2, i + 1);
    }
    EXPECT_FALSE(bus->AreFramesZero());

    // Fill the bus with dummy data.
    for (int i = 0; i < bus->channels(); ++i)
        std::fill(bus->channel(i), bus->channel(i) + bus->frames(), i + 1);

    // Zero all the frames of each channel.
    bus->Zero();
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("All Zero");
        VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), 0);
    }
    EXPECT_TRUE(bus->AreFramesZero());
}

// Each test vector represents two channels of data in the following arbitrary
// layout: <min, zero, max, min, max / 2, min / 2, zero, max, zero, zero>.
static const int kTestVectorSize = 10;
static const uint8_t kTestVectorUint8[kTestVectorSize] = {
    0, -INT8_MIN, UINT8_MAX,
    0, INT8_MAX / 2 + 128, INT8_MIN / 2 + 128,
    -INT8_MIN, UINT8_MAX, -INT8_MIN,
    -INT8_MIN
};
static const int16_t kTestVectorInt16[kTestVectorSize] = {
    INT16_MIN, 0, INT16_MAX, INT16_MIN, INT16_MAX / 2,
    INT16_MIN / 2, 0, INT16_MAX, 0, 0
};
static const int32_t kTestVectorInt32[kTestVectorSize] = {
    INT32_MIN, 0, INT32_MAX, INT32_MIN, INT32_MAX / 2,
    INT32_MIN / 2, 0, INT32_MAX, 0, 0
};
static const float kTestVectorFloat32[kTestVectorSize] = {
    -1.0f, 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f
};

// Expected results.
static const int kTestVectorFrameCount = kTestVectorSize / 2;
static const float kTestVectorResult[][kTestVectorFrameCount] = {
    { -1.0f, 1.0f, 0.5f, 0.0f, 0.0f },
    { 0.0f, -1.0f, -0.5f, 1.0f, 0.0f }
};
static const int kTestVectorChannelCount = arraysize(kTestVectorResult);

// Verify FromInterleaved() deinterleaves audio in supported formats correctly.
TEST_F(AudioBusTest, FromInterleaved)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    std::unique_ptr<AudioBus> expected = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    for (int ch = 0; ch < kTestVectorChannelCount; ++ch) {
        memcpy(expected->channel(ch), kTestVectorResult[ch],
            kTestVectorFrameCount * sizeof(*expected->channel(ch)));
    }

    // Test deprecated version that takes |bytes_per_sample| as an input.
    {
        SCOPED_TRACE("uint8_t");
        bus->Zero();
        bus->FromInterleaved(kTestVectorUint8, kTestVectorFrameCount,
            sizeof(*kTestVectorUint8));

        // Biased uint8_t calculations have poor precision, so the epsilon here is
        // slightly more permissive than int16_t and int32_t calculations.
        VerifyAreEqualWithEpsilon(bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint8_t>::max() - 1));
    }
    {
        SCOPED_TRACE("int16_t");
        bus->Zero();
        bus->FromInterleaved(kTestVectorInt16, kTestVectorFrameCount,
            sizeof(*kTestVectorInt16));
        VerifyAreEqualWithEpsilon(
            bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint16_t>::max() + 1.0f));
    }
    {
        SCOPED_TRACE("int32_t");
        bus->Zero();
        bus->FromInterleaved(kTestVectorInt32, kTestVectorFrameCount,
            sizeof(*kTestVectorInt32));

        VerifyAreEqualWithEpsilon(
            bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint32_t>::max() + 1.0f));
    }

    // Test non-deprecated version that takes SampleTypeTraits as a template
    // parameter.
    {
        SCOPED_TRACE("UnsignedInt8SampleTypeTraits");
        bus->Zero();
        bus->FromInterleaved<UnsignedInt8SampleTypeTraits>(kTestVectorUint8,
            kTestVectorFrameCount);
        // Biased uint8_t calculations have poor precision, so the epsilon here is
        // slightly more permissive than int16_t and int32_t calculations.
        VerifyAreEqualWithEpsilon(bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint8_t>::max() - 1));
    }
    {
        SCOPED_TRACE("SignedInt16SampleTypeTraits");
        bus->Zero();
        bus->FromInterleaved<SignedInt16SampleTypeTraits>(kTestVectorInt16,
            kTestVectorFrameCount);
        VerifyAreEqualWithEpsilon(
            bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint16_t>::max() + 1.0f));
    }
    {
        SCOPED_TRACE("SignedInt32SampleTypeTraits");
        bus->Zero();
        bus->FromInterleaved<SignedInt32SampleTypeTraits>(kTestVectorInt32,
            kTestVectorFrameCount);
        VerifyAreEqualWithEpsilon(
            bus.get(), expected.get(),
            1.0f / (std::numeric_limits<uint32_t>::max() + 1.0f));
    }
    {
        SCOPED_TRACE("Float32SampleTypeTraits");
        bus->Zero();
        bus->FromInterleaved<Float32SampleTypeTraits>(kTestVectorFloat32,
            kTestVectorFrameCount);
        VerifyAreEqual(bus.get(), expected.get());
    }
}

// Verify FromInterleavedPartial() deinterleaves audio correctly.
TEST_F(AudioBusTest, FromInterleavedPartial)
{
    // Only deinterleave the middle two frames in each channel.
    static const int kPartialStart = 1;
    static const int kPartialFrames = 2;
    ASSERT_LE(kPartialStart + kPartialFrames, kTestVectorFrameCount);

    std::unique_ptr<AudioBus> bus = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    std::unique_ptr<AudioBus> expected = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    expected->Zero();
    for (int ch = 0; ch < kTestVectorChannelCount; ++ch) {
        memcpy(expected->channel(ch) + kPartialStart,
            kTestVectorResult[ch] + kPartialStart,
            kPartialFrames * sizeof(*expected->channel(ch)));
    }

    // Test deprecated version that takes |bytes_per_sample| as an input.
    {
        SCOPED_TRACE("int32_t");
        bus->Zero();
        bus->FromInterleavedPartial(
            kTestVectorInt32 + kPartialStart * bus->channels(), kPartialStart,
            kPartialFrames, sizeof(*kTestVectorInt32));
        VerifyAreEqual(bus.get(), expected.get());
    }

    // Test non-deprecated version that takes SampleTypeTraits as a template
    // parameter.
    {
        SCOPED_TRACE("SignedInt32SampleTypeTraits");
        bus->Zero();
        bus->FromInterleavedPartial<SignedInt32SampleTypeTraits>(
            kTestVectorInt32 + kPartialStart * bus->channels(), kPartialStart,
            kPartialFrames);
        VerifyAreEqual(bus.get(), expected.get());
    }
}

// Verify ToInterleaved() interleaves audio in suported formats correctly.
TEST_F(AudioBusTest, ToInterleaved)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    // Fill the bus with our test vector.
    for (int ch = 0; ch < bus->channels(); ++ch) {
        memcpy(bus->channel(ch), kTestVectorResult[ch],
            kTestVectorFrameCount * sizeof(*bus->channel(ch)));
    }

    // Test deprecated version that takes |bytes_per_sample| as an input.
    {
        SCOPED_TRACE("uint8_t");
        uint8_t test_array[arraysize(kTestVectorUint8)];
        bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorUint8), test_array);
        ASSERT_EQ(0,
            memcmp(test_array, kTestVectorUint8, sizeof(kTestVectorUint8)));
    }
    {
        SCOPED_TRACE("int16_t");
        int16_t test_array[arraysize(kTestVectorInt16)];
        bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt16), test_array);
        ASSERT_EQ(0,
            memcmp(test_array, kTestVectorInt16, sizeof(kTestVectorInt16)));
    }
    {
        SCOPED_TRACE("int32_t");
        int32_t test_array[arraysize(kTestVectorInt32)];
        bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt32), test_array);

        // Some compilers get better precision than others on the half-max test, so
        // let the test pass with an off by one check on the half-max.
        int32_t alternative_acceptable_result[arraysize(kTestVectorInt32)];
        memcpy(alternative_acceptable_result, kTestVectorInt32,
            sizeof(kTestVectorInt32));
        ASSERT_EQ(alternative_acceptable_result[4],
            std::numeric_limits<int32_t>::max() / 2);
        alternative_acceptable_result[4]++;

        ASSERT_TRUE(
            memcmp(test_array, kTestVectorInt32, sizeof(kTestVectorInt32)) == 0 || memcmp(test_array, alternative_acceptable_result, sizeof(alternative_acceptable_result)) == 0);
    }

    // Test non-deprecated version that takes SampleTypeTraits as a template
    // parameter.
    {
        SCOPED_TRACE("UnsignedInt8SampleTypeTraits");
        uint8_t test_array[arraysize(kTestVectorUint8)];
        bus->ToInterleaved<UnsignedInt8SampleTypeTraits>(bus->frames(), test_array);
        ASSERT_EQ(0,
            memcmp(test_array, kTestVectorUint8, sizeof(kTestVectorUint8)));
    }
    {
        SCOPED_TRACE("SignedInt16SampleTypeTraits");
        int16_t test_array[arraysize(kTestVectorInt16)];
        bus->ToInterleaved<SignedInt16SampleTypeTraits>(bus->frames(), test_array);
        ASSERT_EQ(0,
            memcmp(test_array, kTestVectorInt16, sizeof(kTestVectorInt16)));
    }
    {
        SCOPED_TRACE("SignedInt32SampleTypeTraits");
        int32_t test_array[arraysize(kTestVectorInt32)];
        bus->ToInterleaved<SignedInt32SampleTypeTraits>(bus->frames(), test_array);

        // Some compilers get better precision than others on the half-max test, so
        // let the test pass with an off by one check on the half-max.
        int32_t alternative_acceptable_result[arraysize(kTestVectorInt32)];
        memcpy(alternative_acceptable_result, kTestVectorInt32,
            sizeof(kTestVectorInt32));
        ASSERT_EQ(alternative_acceptable_result[4],
            std::numeric_limits<int32_t>::max() / 2);
        alternative_acceptable_result[4]++;

        ASSERT_TRUE(
            memcmp(test_array, kTestVectorInt32, sizeof(kTestVectorInt32)) == 0 || memcmp(test_array, alternative_acceptable_result, sizeof(alternative_acceptable_result)) == 0);
    }
    {
        SCOPED_TRACE("Float32SampleTypeTraits");
        float test_array[arraysize(kTestVectorFloat32)];
        bus->ToInterleaved<Float32SampleTypeTraits>(bus->frames(), test_array);
        ASSERT_EQ(
            0, memcmp(test_array, kTestVectorFloat32, sizeof(kTestVectorFloat32)));
    }
}

// Verify ToInterleavedPartial() interleaves audio correctly.
TEST_F(AudioBusTest, ToInterleavedPartial)
{
    // Only interleave the middle two frames in each channel.
    static const int kPartialStart = 1;
    static const int kPartialFrames = 2;
    ASSERT_LE(kPartialStart + kPartialFrames, kTestVectorFrameCount);

    std::unique_ptr<AudioBus> expected = AudioBus::Create(kTestVectorChannelCount, kTestVectorFrameCount);
    for (int ch = 0; ch < kTestVectorChannelCount; ++ch) {
        memcpy(expected->channel(ch), kTestVectorResult[ch],
            kTestVectorFrameCount * sizeof(*expected->channel(ch)));
    }

    // Test deprecated version that takes |bytes_per_sample| as an input.
    {
        SCOPED_TRACE("int16_t");
        int16_t test_array[arraysize(kTestVectorInt16)];
        expected->ToInterleavedPartial(kPartialStart, kPartialFrames,
            sizeof(*kTestVectorInt16), test_array);
        ASSERT_EQ(0, memcmp(test_array, kTestVectorInt16 + kPartialStart * kTestVectorChannelCount, kPartialFrames * sizeof(*kTestVectorInt16) * kTestVectorChannelCount));
    }

    // Test non-deprecated version that takes SampleTypeTraits as a template
    // parameter.
    {
        SCOPED_TRACE("Float32SampleTypeTraits");
        float test_array[arraysize(kTestVectorFloat32)];
        expected->ToInterleavedPartial<Float32SampleTypeTraits>(
            kPartialStart, kPartialFrames, test_array);
        ASSERT_EQ(0, memcmp(test_array, kTestVectorFloat32 + kPartialStart * kTestVectorChannelCount, kPartialFrames * sizeof(*kTestVectorFloat32) * kTestVectorChannelCount));
    }
}

struct ZeroingOutTestData {
    static constexpr int kChannelCount = 2;
    static constexpr int kFrameCount = 10;
    static constexpr int kInterleavedFrameCount = 3;

    std::unique_ptr<AudioBus> bus_under_test;
    std::vector<float> interleaved_dummy_frames;

    ZeroingOutTestData()
    {
        // Create a bus and fill each channel with a test pattern of form
        // [1.0, 2.0, 3.0, ...]
        bus_under_test = AudioBus::Create(kChannelCount, kFrameCount);
        for (int ch = 0; ch < kChannelCount; ++ch) {
            auto* sample_array_for_current_channel = bus_under_test->channel(ch);
            for (int frame_index = 0; frame_index < kFrameCount; frame_index++) {
                sample_array_for_current_channel[frame_index] = static_cast<float>(frame_index + 1);
            }
        }

        // Create a vector containing dummy interleaved samples.
        static const float kDummySampleValue = 0.123f;
        interleaved_dummy_frames.resize(kChannelCount * kInterleavedFrameCount);
        std::fill(interleaved_dummy_frames.begin(), interleaved_dummy_frames.end(),
            kDummySampleValue);
    }
};

TEST_F(AudioBusTest, FromInterleavedZerosOutUntouchedFrames)
{
    ZeroingOutTestData test_data;

    // Exercise
    test_data.bus_under_test->FromInterleaved<Float32SampleTypeTraits>(
        &test_data.interleaved_dummy_frames[0], test_data.kInterleavedFrameCount);

    // Verification
    for (int ch = 0; ch < test_data.kChannelCount; ++ch) {
        auto* sample_array_for_current_channel = test_data.bus_under_test->channel(ch);
        for (int frame_index = test_data.kInterleavedFrameCount;
             frame_index < test_data.kFrameCount; frame_index++) {
            ASSERT_EQ(0.0f, sample_array_for_current_channel[frame_index]);
        }
    }
}

TEST_F(AudioBusTest, FromInterleavedPartialDoesNotZeroOutUntouchedFrames)
{
    {
        SCOPED_TRACE("Zero write offset");

        ZeroingOutTestData test_data;
        static const int kWriteOffsetInFrames = 0;

        // Exercise
        test_data.bus_under_test->FromInterleavedPartial<Float32SampleTypeTraits>(
            &test_data.interleaved_dummy_frames[0], kWriteOffsetInFrames,
            test_data.kInterleavedFrameCount);

        // Verification
        for (int ch = 0; ch < test_data.kChannelCount; ++ch) {
            auto* sample_array_for_current_channel = test_data.bus_under_test->channel(ch);
            for (int frame_index = test_data.kInterleavedFrameCount + kWriteOffsetInFrames;
                 frame_index < test_data.kFrameCount; frame_index++) {
                ASSERT_EQ(frame_index + 1,
                    sample_array_for_current_channel[frame_index]);
            }
        }
    }
    {
        SCOPED_TRACE("Positive write offset");

        ZeroingOutTestData test_data;
        static const int kWriteOffsetInFrames = 2;

        // Exercise
        test_data.bus_under_test->FromInterleavedPartial<Float32SampleTypeTraits>(
            &test_data.interleaved_dummy_frames[0], kWriteOffsetInFrames,
            test_data.kInterleavedFrameCount);

        // Verification
        for (int ch = 0; ch < test_data.kChannelCount; ++ch) {
            auto* sample_array_for_current_channel = test_data.bus_under_test->channel(ch);
            // Check untouched frames before write offset
            for (int frame_index = 0; frame_index < kWriteOffsetInFrames;
                 frame_index++) {
                ASSERT_EQ(frame_index + 1,
                    sample_array_for_current_channel[frame_index]);
            }
            // Check untouched frames after write
            for (int frame_index = test_data.kInterleavedFrameCount + kWriteOffsetInFrames;
                 frame_index < test_data.kFrameCount; frame_index++) {
                ASSERT_EQ(frame_index + 1,
                    sample_array_for_current_channel[frame_index]);
            }
        }
    }
}

TEST_F(AudioBusTest, Scale)
{
    std::unique_ptr<AudioBus> bus = AudioBus::Create(kChannels, kFrameCount);

    // Fill the bus with dummy data.
    static const float kFillValue = 1;
    for (int i = 0; i < bus->channels(); ++i)
        std::fill(bus->channel(i), bus->channel(i) + bus->frames(), kFillValue);

    // Adjust by an invalid volume and ensure volume is unchanged.
    bus->Scale(-1);
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("Invalid Scale");
        VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), kFillValue);
    }

    // Verify correct volume adjustment.
    static const float kVolume = 0.5;
    bus->Scale(kVolume);
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("Half Scale");
        VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(),
            kFillValue * kVolume);
    }

    // Verify zero volume case.
    bus->Scale(0);
    for (int i = 0; i < bus->channels(); ++i) {
        SCOPED_TRACE("Zero Scale");
        VerifyArrayIsFilledWithValue(bus->channel(i), bus->frames(), 0);
    }
}

} // namespace media
